﻿// Copyright 2010 Maxime Serrano <lastchronicler@gmail.com>
// Copyright 2009, 2010 Matvei Stefarov <me@matvei.org>
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Text;
using MCSharp.Network;

namespace MCSharp {
    public sealed class Session {
        public Player player;
        public DateTime loginTime;
        public bool canReceive,
                    canSend,
                    canQueue,
                    canDispose;

        Thread ioThread;
        TcpClient client;
        BinaryReader reader;
        PacketWriter writer;
        public ConcurrentQueue<Packet> outputQueue, priorityOutputQueue;

        internal int fullPositionUpdateCounter;
        internal const int fullPositionUpdateInterval = 20;
        internal bool hasRegistered = false;

        public Session(TcpClient _client) {
            loginTime = DateTime.Now;

            canReceive = true;
            canQueue = true;

            outputQueue = new ConcurrentQueue<Packet>();
            priorityOutputQueue = new ConcurrentQueue<Packet>();

            client = _client;
            client.SendTimeout = 10000;
            client.ReceiveTimeout = 10000;

            reader = new BinaryReader(client.GetStream());
            writer = new PacketWriter(client.GetStream());

            Logger.Log("Server.CheckConnections: Incoming connection from " + GetIP().ToString(), LogType.Debug);

            ioThread = new Thread(IoLoop);
            ioThread.IsBackground = true;
            ioThread.Start();
        }

        void IoLoop() {
            byte opcode, lastopcode;
            opcode = lastopcode = 0;
            Packet packet = new Packet();

            int pollInterval = 200;
            int pollCounter = 0;
            int packetsSent = 0;

            try {
                LoginSequence();
                canSend = true;

                if (player == null) return;

                while (!canDispose) {
                    Thread.Sleep(1);

                    packetsSent = 0;

                    // Detect player disconnect
                    if (pollCounter > pollInterval) {
                        if (!client.Connected ||
                            (client.Client.Poll(1000, SelectMode.SelectRead) && client.Client.Available == 0)) {
                            if (player != null) {
                                Logger.Log("Session.IoLoop: Lost connection to player {0} ({1}).", LogType.Debug, player.GetLogName(), GetIP());
                            } else {
                                Logger.Log("Session.IoLoop: Lost connection to unidentified player {0}.", LogType.Debug, GetIP());
                            }
                            return;
                        }
                        pollCounter = 0;
                    }
                    pollCounter++;
                    
                    // Send priority output to player
                    while (canSend && packetsSent < Server.maxSessionPacketsPerTick) {
                        if (!priorityOutputQueue.Dequeue(ref packet)) break;
                        writer.Write(packet.data);
                        packetsSent++;
                        if (packet.data[0] == (byte)SMSG.Disconnect) {
                            Logger.Log("Session.IoLoop: Kick packet delivered to {0}.", LogType.Debug, player.GetLogName());
                            return;
                        }
                    }

                    // Send output to player
                    while (canSend && packetsSent < Server.maxSessionPacketsPerTick) {
                        if (!outputQueue.Dequeue(ref packet)) break;
                        writer.Write(packet.data);
                        packetsSent++;
                        if (packet.data[0] == (byte)SMSG.Disconnect) {
                            writer.Flush();
                            Logger.Log("Session.IoLoop: Kick packet delivered to {0}.", LogType.Debug, player.GetLogName());
                            return;
                        }
                    }

                    // Get input from player
                    while (canReceive && client.GetStream().DataAvailable) {
                        lastopcode = opcode;
                        opcode = reader.ReadByte();
                        PacketHandlers.HandlePacket(reader, this, player, opcode, lastopcode);
                    }
                }
            } catch (ThreadAbortException ex) {
                Logger.Log("Session.IoLoop: Thread aborted: {0}", LogType.Error, ex);
            } catch (IOException ex) {
                Logger.Log("Session.IoLoop: {0}", LogType.Debug, ex.Message);
            } catch (SocketException ex) {
                Logger.Log("Session.IoLoop: {0}", LogType.Debug, ex.Message);
#if DEBUG
                // CRASH on errors
#else
            } catch( Exception ex ) {
                Logger.Log( "Session.IoLoop: {0}", LogType.Error, ex );
#endif
            } finally {
                canQueue = false;
                canSend = false;
                canDispose = true;
            }
        }

        void LoginSequence() {
            // Handshake
            byte opcode = reader.ReadByte();
            if (opcode != (byte)CMSG.Handshake) {
                Logger.Log("Session.LoginSequence: Unexpected opcode in the first packet: {0}", LogType.Error, opcode);
                KickNow("Unexpected handshake message - possible protocol mismatch!");
                return;
            }

            PacketHandlers.HandlePacket(reader, this, player, opcode, 0);

            // Login
            opcode = reader.ReadByte();
            if (opcode != (byte)CMSG.Login) {
                Logger.Log("Session.LoginSequence: Unexpected opcode in the second packet: {0}", LogType.Error, opcode);
                KickNow("Unexpected login message - possible protocol mismatch!");
                return;
            }

            PacketHandlers.HandlePacket(reader, this, player, opcode, (byte)CMSG.Handshake);
        }

        public void Disconnect() {
            if (player != null) {
                Server.UnregisterPlayer(player);
                player = null;
            }

            if (ioThread != null) {
                if (ioThread.IsAlive) {
                    ioThread.Abort();
                }
                ioThread = null;
            }

            if (reader != null) {
                reader.Close();
                reader = null;
            }

            if (writer != null) {
                writer.Close();
                writer = null;
            }

            if (client != null) {
                client.Close();
                client = null;
            }
        }

        public IPAddress GetIP() {
            return ((IPEndPoint)(client.Client.RemoteEndPoint)).Address;
        }


        public void Send(Packet packet) {
            Send(packet, true);
        }

        public void Send(Packet packet, bool isHighPriority) {
            if (canQueue) {
                if (isHighPriority) {
                    priorityOutputQueue.Enqueue(packet);
                } else {
                    outputQueue.Enqueue(packet);
                }
            }
        }

        // warning: not thread safe. should ONLY be called from IoThread
        public void SendNow(Packet packet) {
            writer.Write(packet.data);
        }

        public void Kick(string message) {
            Send(PacketWriter.MakeDisconnect(message));
            canReceive = false;
            canQueue = false;
            canDispose = true;
        }

        public void KickNow(string message) {
            SendNow(PacketWriter.MakeDisconnect(message));
            writer.Flush();
            canReceive = false;
            canSend = false;
            canQueue = false;
            canDispose = true;
        }

        internal void Write(Packet p) {
            writer.Write(p);
        }
    }
}
